from typing import Any, Union, List, Tuple, Iterable, Optional, Generator

__all__ = ['flatten', 'isconsistentinstance']

def _flatten_impl(_x: Iterable, depth: Optional[int] = None) -> Generator:
    if depth is None:
        for __x in _x:
            if not isinstance(__x, (List, Tuple)):
                yield __x
            else:
                yield from flatten(__x, depth)
    elif depth > 0:
        for __x in _x:
            if not isinstance(__x, (List, Tuple)):
                yield __x
            else:
                yield from flatten(__x, depth-1)
    elif depth == 0:
        for __x in _x:
            yield __x
    else:
        yield _x

def flatten(_x: Union[List, Tuple], depth: Optional[int] = None) -> List:
    if not isinstance(_x, (List, Tuple)):
        raise TypeError(f"excepted input as Tuple or List, "
                        f"but got input of type {type(_x)} instead")
    if not isinstance(depth, (int, type(None))):
        raise ValueError("excepted argument 'depth' as a positive integer, "
                         "if you do not want to specify the depth, pass NoneType instead")
    return list(_flatten_impl(_x, depth))

def _check_consistent_class(_x: Any) -> bool:
    if len(_x) == 1:
        return True
    else:
        _class = type(_x[0])
        for __x in _x[1:]:
            if type(__x) != _class:
                return False
        return True

def isconsistentinstance(_x: Iterable, _t: Union[type, Tuple[Union[type, Tuple[Any, ...]], ...]]) -> bool:
    if _check_consistent_class(_x):
        return isinstance(_x[0], _t)
    else:
        return False

def list2dict(_list: List) -> dict:
    if not isinstance(_list, List):
        raise TypeError(f"excepted input as List, got {type(_list)} instead")
    if len(_list) % 2 != 0:
            raise ValueError(f"input decay_kwargs has odd length {len(_list)}, "
                             f"excepted input list must be a list of <key, value> pairs")
    _dict = {}
    for i in range(0, len(_list), 2):
        if not isinstance(_list[i], str):
            raise ValueError(f"excepted input in list[{i}] as a keyword, "
                             f"a keyword must be a string, got a {type(_list)}")
        _dict[_list[i]] = _list[i+1]
    return _dict
